#ifndef PRYN_PIN_H
#define PRYN_PIN_H

typedef struct PrynPin PrynPin;
typedef enum PrynPinDirection PrynPinDirection;
typedef struct PrynPinList PrynPinList;

#include <pryn/component.h>
#include <pryn/object.h>
#include <pryn/state.h>
#include <pryn/string.h>
#include <pryn/value.h>

/// A double-linked list of pins.
struct PrynPinList
{
	PrynPin *first; /// First in the list of pins or 0 for none.
	PrynPin *last; /// Last in the list of pins or 0 for none.

#if __cplusplus
	inline size_t count (); ///< Return the number of elements in the pin list.
#endif /* __cplusplus */
};

/// Whether a pin is output or input.
enum PrynPinDirection
{
	PrynPinDirectionNone, /// No direction specified or known. This is an invalid value for most operations.
	PrynPinDirectionOutput, /// An output pin.
	PrynPinDirectionInput, /// An input pin.
};

/** @defgroup PrynPin PrynPin C API
	@{ */

PrynImport (PrynResult, prynPinToObject, (PrynPin *pin, PrynObject *object))
	/**< @brief Wrap the pin into an object.

	@param pin The pin to wrap.
	@param[out] object Written with the wrapped pin object of type #PrynObjectTypePin, or cleared to an empty object of type #PrynObjectTypeNone on failure.
	@returns #PrynResultSuccess or an error code; see #PrynResult. */

PrynImport (PrynResult, prynPinCreate, (PrynPin **pin, PrynComponent *component, PrynPinDirection direction, const PrynString *id, const PrynString *name, const PrynString *description))
	/**< @brief Create a pin on the component.

	To save a few keystrokes, #prynPinCreateOutput and #prynPinCreateInput can be used instead.

	@param[out] pin Receives the created pin, or 0 if there was a failure.
	@param component The component to create a pin on.
	@param direction The direction the pin should be in; this can be #PrynPinDirectionInput or #PrynPinDirectionOutput only.
	@param id Optionally an identifier for a pin. This does not need to be unique. The value is adopted, not copied.
	@param name The name of the pin. The value is adopted, not copied.
	@param description The description of the pin. The value is adopted, not copied.
	@returns #PrynResultSuccess or an error code; see #PrynResult. */

PrynImport (PrynResult, prynPinCreateOutput, (PrynPin **pin, PrynComponent *component, const PrynString *id, const PrynString *name, const PrynString *description))
	/**< @brief Create an output pin on the component.

	@param[out] pin Receives the created pin, or 0 if there was a failure.
	@param component The component to create a pin on.
	@param id Optionally an identifier for a pin. This does not need to be unique. The value is adopted, not copied.
	@param name The name of the pin. The value is adopted, not copied.
	@param description The description of the pin. The value is adopted, not copied.
	@returns #PrynResultSuccess or an error code; see #PrynResult. */

PrynImport (PrynResult, prynPinCreateInput, (PrynPin **pin, PrynComponent *component, const PrynString *id, const PrynString *name, const PrynString *description))
	/**< @brief Create an output pin on the component.

	@param component The component to create a pin on.
	@param[out] pin Receives the created pin, or 0 if there was a failure.
	@param id Optionally an identifier for a pin. This does not need to be unique. The value is adopted, not copied.
	@param name The name of the pin. The value is adopted, not copied.
	@param description The description of the pin. The value is adopted, not copied.
	@returns #PrynResultSuccess or an error code; see #PrynResult. */

PrynImport (PrynResult, prynPinDestroy, (PrynPin *pin))
	/**< @brief Destroy a pin, removing it from the component.

	@param pin The pin to destroy. After calling this function, using the pointer is illegal and will cause memory corruption.
	@returns #PrynResultSuccess or error code; see #PrynResult. */

PrynImport (PrynResult, prynPinDisconnect, (PrynPin *pin))
	/**< @brief Disconnect the pin if it's connected to anything.

	@param pin The pin to disconnect.
	@returns #PrynResultSuccess or error code; see #PrynResult. */

PrynImport (PrynResult, prynPinState, (PrynPin *pin, PrynState **output)) ///< Return the state for a pin.
PrynImport (PrynResult, prynPinComponent, (PrynPin *pin, PrynComponent **output)) ///< The component this pin is defined on.
PrynImport (PrynResult, prynPinValue, (PrynPin *pin, PrynValue **output)) ///< The value of this pin.
PrynImport (PrynResult, prynPinTags, (PrynPin *pin, PrynTagList **output)) ///< List of tags associated with the pin.
PrynImport (PrynResult, prynPinConnected, (PrynPin *pin, PrynPin **output)) ///< The other pin this pin is connected to..
PrynImport (PrynResult, prynPinNext, (PrynPin *pin, PrynPin **result)) ///< Next input/output pin in the component or 0 if this is the last.
PrynImport (PrynResult, prynPinPrevious, (PrynPin *pin, PrynPin **result)) ///< Previous input/output pin in the component or 0 if this is the first.
PrynImport (PrynResult, prynPinList, (PrynPin *pin, PrynPinList **result)) ///< Retrieve the list of pins in the component this pin is in, either input or output pins.

PrynImport (PrynResult, prynPinIsConnected, (PrynPin *pin)) ///< Whether the pin is connected to anything; #PrynResultSuccess if so, #PrynResultDone if not.
PrynImport (PrynResult, prynPinIsOutput, (PrynPin *pin)) ///< Whether this is an output pin; #PrynResultSuccess if so, #PrynResultDone if not.
PrynImport (PrynResult, prynPinIsInput, (PrynPin *pin)) ///< Whether this is an input pin; #PrynResultSuccess if so, #PrynResultDone if not.
PrynImport (PrynResult, prynPinIsLast, (PrynPin *pin)) ///< Whether this is the last pin in the component (either input or output); #PrynResultSuccess if so, #PrynResultDone if not.
PrynImport (PrynResult, prynPinIsFirst, (PrynPin *pin)) ///< Whether this is the first pin in the component (either input or output); #PrynResultSuccess if so, #PrynResultDone if not.

PrynImport (PrynResult, prynPinSetValue, (PrynPin *pin, PrynValue *value)) ///< Set the value of the pin.
PrynImport (PrynResult, prynPinSetType, (PrynPin *pin, PrynType *value)) ///< Set the type values of the pin need to be. If this is 0, then any type can be set to the pin.
PrynImport (PrynResult, prynPinChangeValue, (PrynPin *pin)) ///< Notification that the pin's value has been changed in place.

PrynImport (PrynResult, prynPinCanConnect, (PrynPin *from, PrynPin *to)) ///< Return whether these pins can be connected (with #PrynResultSuccess). If they can't be connected, #PrynResultDone is returned.
PrynImport (PrynResult, prynPinConnect, (PrynPin *from, PrynPin *to)) ///< Connect these pins.

PrynImport (PrynResult, prynPinListCount, (PrynPinList *list, size_t *output)) ///< Return the number of elements in the pin list.

#ifdef PrynInternalStructs
/** @brief An input or output from a component.
*/
struct PrynPin
{
#ifdef PrynInternal
/** @name Internal fields
	These fields are not normally visible to clients, and it is recommended not to use them to maximise binary compatibility. To make them available, define #PrynInternal.
	@{ */

	PrynCommonObject pCommonObject; ///< Common fields you'd expect in an object, like tags and monitors.
	PrynCommonResource pCommonResource; ///< Common fields you'd expect in a resource, like name and description.

	PrynPin *pNext; ///< Next pin in the list or 0 if this is the last.
	PrynPin *pPrevious; ///< Previous pin in the list or 0 if this is the first.
	PrynComponent *pComponent; ///< Component this is a pin for.
	PrynPinDirection pDirection; ///< Whether this is an input or output pin.
	PrynPin *pConnected; ///< The other pin we're connected to or 0 if this is unconnected.
	PrynType *pType; ///< Type required on the pin or 0 if anything works.
	
	PrynString pId; ///< Optional identifier of the pin. This may be empty for generated pins.
	PrynValue *pValue; ///< Current value of the pin, 0 for none.

/** @} */
#endif /* PrynInternal */

#if __cplusplus
	static inline PrynPin *Create (PrynComponent *component, PrynPinDirection direction, const PrynString &id, const PrynString &name, const PrynString &description);
		/**< @brief Create a pin on the component.

		To save a few keystrokes, #CreateOutput and #CreateInput can be used instead.

		@param component The component to create a pin on.
		@param direction The direction the pin should be in; this can be #PrynPinDirectionInput or #PrynPinDirectionOutput only.
		@param id Optionally an identifier for a pin. This does not need to be unique. The value is adopted, not copied.
		@param name The name of the pin. The value is adopted, not copied.
		@param description The description of the pin. The value is adopted, not copied.
		@returns The created pin. */

	static inline PrynPin *CreateOutput (PrynComponent *component, const PrynString &id, const PrynString &name, const PrynString &description);
		/**< @brief Create an output pin on the component.

		@param component The component to create a pin on.
		@param id Optionally an identifier for a pin. This does not need to be unique. The value is adopted, not copied.
		@param name The name of the pin. The value is adopted, not copied.
		@param description The description of the pin. The value is adopted, not copied.
		@returns The new pin. */

	static inline PrynPin *CreateInput (PrynComponent *component, const PrynString &id, const PrynString &name, const PrynString &description);
		/**< @brief Create an input pin on the component.

		@param component The component to create a pin on.
		@param id Optionally an identifier for a pin. This does not need to be unique. The value is adopted, not copied.
		@param name The name of the pin. The value is adopted, not copied.
		@param description The description of the pin. The value is adopted, not copied.
		@returns The new pin. */

	inline PrynResult checkError (PrynResult result) { return prynCheckErrorBase (state (), result); } ///< If the result is below zero, throw an error.

	inline void destroy () { prynPinDestroy (this); }
		/**< @brief Destroy a pin, removing it from the component.
		
		This cannot throw an exception. After destroying the pin, accessing this object in any way is illegal and will lead to memory corruption. */

	inline PrynObject toObject () { PrynObject object; checkError (prynPinToObject (this, &object)); return object; }
		/**< @brief Wrap the pin into an object.

		@returns An object of type #PrynObjectTypePin. */

	inline void changeValue () { checkError (prynPinChangeValue (this)); } ///< Notification that the value of the pin has changed in place.

	inline bool canConnect (PrynPin *other) { return PrynIsTrue (checkError (prynPinCanConnect (this, other))); } ///< Return whether these pins can be connected.
	inline void connect (PrynPin *other) { checkError (prynPinConnect (this, other)); } ///< Connect these pins.
	inline void disconnect () { checkError (prynPinDisconnect (this)); } /**< @brief Disconnect the pin if it's connected to anything. */

/** @name Properties
@{ */

	inline PrynState *state () { PrynState *output; prynCheckErrorNull (prynPinState (this, &output)); return output; } ///< The state the pin exists within.
	inline PrynComponent *component () { PrynComponent *output; checkError (prynPinComponent (this, &output)); return output; } ///< The component this pin is defined on.
	inline void type (PrynType *value) { checkError (prynPinSetType (this, value)); } ///< Set the type values of the pin need to be; if 0, any type can be set to the pin.
	inline PrynValue *value () { PrynValue *output; checkError (prynPinValue (this, &output)); return output; } ///< The value of this pin.
	inline void value (PrynValue *value) { checkError (prynPinSetValue (this, value)); } ///< Set the value of the pin.
	inline PrynPinList *list () {PrynPinList *output; checkError (prynPinList (this, &output)); return output; } ///< The list of component pins this pin is contained within (output or input list).
	inline PrynTagList *tags () { PrynTagList *output; checkError (prynPinTags (this, &output)); return output; } ///< List of tags associated with the pin.
	inline PrynPin *connected () { PrynPin *output; checkError (prynPinConnected (this, &output)); return output; } ///< The pin this pin is connected to or 0 if it's not connected to anything.
	inline bool isConnected () { return PrynIsTrue (checkError (prynPinIsConnected (this))); } ///< Whether the pin is connected to anything.
	inline bool isOutput () { return PrynIsTrue (checkError (prynPinIsOutput (this))); } ///< Whether this is an output pin.
	inline bool isInput () { return PrynIsTrue (checkError (prynPinIsInput (this))); } ///< Whether this is an input pin.
	inline bool isLast () { return PrynIsTrue (checkError (prynPinIsLast (this))); } ///< Whether this is the last pin of its direction in the component.
	inline bool isFirst () { return PrynIsTrue (checkError (prynPinIsFirst (this))); } ///< Whether this is the first pin of its direction in the component.
	inline PrynPin *next () { PrynPin *output; checkError (prynPinNext (this, &output)); return output; } ///< Next pin of this direction in the component or 0 if this is the last.
	inline PrynPin *previous () { PrynPin *output; checkError (prynPinPrevious (this, &output)); return output; } ///< Previous pin of this direction in the component or 0 if this is the first.
	
/** @} */

#endif /* __cplusplus */
};
#endif /* PrynInternalStructs */

/** @} */ // (end of PrynPin group)

#endif /* PRYN_PIN_H */
